﻿using System;
using System.Diagnostics;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Drawing;
using System.Threading;
using SnapLib;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using KeyboardRemapper;
using RawInput;

class KeyInterceptor
{
   private const int WH_KEYBOARD_LL = 13;
   private const int WM_KEYDOWN = 0x0100;
   private const int WM_KEYUP = 0x0101;
   private const int WM_KEYCHAR = 0x0102;
   private const int WM_COMMAND = 0x0111;

   private static LowLevelKeyboardHook m_keyboardHook = null;
   private static LowLevelKeyboardProc _proc = HookCallback;

   private static Dictionary<int, bool> m_dicModifiers;
   private static List<Macro> m_lMacros;

   private static Dictionary<int, List<Macro>> m_dicMacros = new Dictionary<int, List<Macro>>();
   private static Dictionary<int, int> m_dicKeyPressedByDevice = new Dictionary<int, int>();
   private static IntPtr _hookID = IntPtr.Zero;
   private static bool m_bPaused = false;
   private static int m_iDisableKeyboard = 0;
   private static HashSet<int> m_EliminateKeyUpEvents = new HashSet<int>();

   public static void DebugLine(string line)
   {
      Debug.WriteLine(line);
   }

   public static void DisableKeyboard()
   {
      m_iDisableKeyboard++;
   }

   public static void EnableKeyboard()
   {
      m_iDisableKeyboard--;
   }

   public static List<Macro> pMacros
   {
      get { return m_lMacros; }
      set { m_lMacros = value; }
   }

   public static bool pPaused
   {
      get { return KeyInterceptor.m_bPaused; }
      set { KeyInterceptor.m_bPaused = value; }
   }

   private static HashSet<int> m_hsUnstopableKeys = new HashSet<int>()
   {
      58,349,694,695,347
   };

   private static Dictionary<int, Keys> m_dicBuiltInModifiers = new Dictionary<int, Keys>()
    {
        {29,Keys.LControlKey},
        {56,Keys.LMenu},
        {42,Keys.LShiftKey},
        {312,Keys.RMenu},
        {285,Keys.RControlKey},
        {310,Keys.RShiftKey}
    };
   private static Dictionary<int, Keys> m_dicBuiltInToggles = new Dictionary<int, Keys>()
    {
        {58,Keys.CapsLock},
        {325,Keys.NumLock},
        {70,Keys.Scroll}
    };


   private static InputDevice m_highLevelHook;

   public static void Start()
   {
      m_lMacros = ReadMacros();
      InitMacros();
      m_highLevelHook = new InputDevice();
      m_highLevelHook.KeyPressed += new InputDevice.DeviceEventHandler(m_highLevelHook_KeyPressed);
      m_keyboardHook = new LowLevelKeyboardHook();
      m_keyboardHook.BlockKey = (key, bKeyDown) =>
         {
            bool bBlock = false;
            int iDevice = -1;

            if (m_dicKeyPressedByDevice.ContainsKey(key))
               iDevice = m_dicKeyPressedByDevice[key];
            if (KeyEvent != null)
               KeyEvent(key, iDevice, bKeyDown);

            if (m_iDisableKeyboard > 0)
               return true;

            if (m_dicModifiers.ContainsKey(key))
            {
               DebugLine(key + " is " + bKeyDown);
               m_dicModifiers[key] = bKeyDown;
            }

            if (m_EliminateKeyUpEvents.Contains(key) && !bKeyDown)
            {
               KeyInterceptor.DebugLine("Block modifier used to call a macro");
               m_EliminateKeyUpEvents.Remove(key);
               bBlock = true;
            }

            if (iDevice != -1)
            {
               if (!m_bPaused)
               {
                  if (m_dicMacros.ContainsKey(key) && !m_hsUnstopableKeys.Contains(key))
                  {
                     Macro selected = null;
                     DebugLine("Execute macro for key " + key + " from device " + iDevice);
                     foreach (Macro m in m_dicMacros[key])
                     {
                        if ((m.pDevice == -1 || m.pDevice == iDevice) && m.pModifiers.All(i => ModifierSet(i)) &&
                            !m.pNotModifiers.Any(i => ModifierSet(i)))
                        {
                           if (selected == null || selected.pModifiers.Count < m.pModifiers.Count)
                              selected = m;
                        }
                     }
                     if (selected != null)
                     {
                        foreach (int Modifier in selected.pModifiers)
                           if (!m_dicBuiltInModifiers.ContainsKey(Modifier))
                              m_EliminateKeyUpEvents.Add(Modifier);
                        selected.Run(bKeyDown);
                        bBlock = true;
                     }
                  }
               }

               if (!bKeyDown)
                  m_dicKeyPressedByDevice.Remove(key);
            }
            return bBlock;
         };
      _hookID = SetHook(_proc);
   }

   static void m_highLevelHook_KeyPressed(object sender, int scanCode, int deviceId)
   {
      m_dicKeyPressedByDevice[scanCode] = deviceId;
   }

   public static void InitMacros()
   {
      m_dicModifiers = m_lMacros.SelectMany(m => m.pModifiers.Where(mod => !m_dicBuiltInModifiers.ContainsKey(mod))).Distinct().ToDictionary(k => k, k => false);
      m_dicMacros.Clear();
      foreach (Macro m in m_lMacros)
      {
         if (!m_dicMacros.ContainsKey(m.pKey))
            m_dicMacros[m.pKey] = new List<Macro>();
         m_dicMacros[m.pKey].Add(m);
      }
   }

   private static List<Macro> ReadMacros()
   {
      List<Macro> macros = new List<Macro>();

      StreamReader sr = new StreamReader(Path.Combine(Application.StartupPath, "macros.txt"));
      while (!sr.EndOfStream)
      {
         Macro m = Macro.ReadMacro(sr);
         if (m != null)
         {
            macros.Add(m);
         }
      }
      return macros;
   }

   public static void End()
   {
      m_keyboardHook.StopHook();
      UnhookWindowsHookEx(_hookID);
   }

   public delegate bool KeyDownHandler(Keys key);

   private static IntPtr SetHook(LowLevelKeyboardProc proc)
   {
      using (Process curProcess = Process.GetCurrentProcess())
      using (ProcessModule curModule = curProcess.MainModule)
      {
         return SetWindowsHookEx(WH_KEYBOARD_LL, proc,
             GetModuleHandle(curModule.ModuleName), 0);
      }
   }

   public delegate void KeyEventDelegate(int code, int device, bool bKeydown);
   public static event KeyEventDelegate KeyEvent;

   private delegate IntPtr LowLevelKeyboardProc(
       int nCode, IntPtr wParam, IntPtr lParam);

   private static IntPtr HookCallback(
       int nCode, IntPtr wParam, IntPtr lParam)
   {
      if (nCode >= 0)
      {
         int scanCode = Marshal.ReadInt32(lParam + 4);
         int flags = Marshal.ReadInt32(lParam + 8);
         bool bExt = (flags & 1) == 1;
         bool bKeyDown = (flags & 0x80) == 0;
         int key = scanCode + (bExt ? 256 : 0);
         bool bBlock = false;

         if (key == 256)
         {
            key = 512 + Marshal.ReadInt32(lParam);
         }

         if (m_dicModifiers.ContainsKey(key))
         {
            DebugLine(key + " is " + bKeyDown);
            m_dicModifiers[key] = bKeyDown;
         }

         if (m_EliminateKeyUpEvents.Contains(scanCode) && !bKeyDown)
         {
            KeyInterceptor.DebugLine("Block modifier used to call a macro");
            m_EliminateKeyUpEvents.Remove(scanCode);
            bBlock = true;
         }

         if ((m_dicMacros.ContainsKey(key) || m_iDisableKeyboard > 0) &&
            (m_hsUnstopableKeys.Contains(key))) 
            // || m_dicMacros[key].Any(m => m.pModifiers.Count > 0)))   If commented in, check below, that only macros with modifiers get executed
         {
            Macro selected = null;
            DebugLine("Execute macro for key " + key);
            foreach (Macro m in m_dicMacros[key])
            {
               DebugLine("Check macro :" + m.ToString());
               if (m.pModifiers.All(i => ModifierSet(i)) &&
                   !m.pNotModifiers.Any(i => ModifierSet(i)))
               {
                  if (selected == null || selected.pModifiers.Count < m.pModifiers.Count)
                     selected = m;
               }
            }
            if (selected != null)
            {
               foreach (int Modifier in selected.pModifiers)
               {
                  if (!m_dicBuiltInModifiers.ContainsKey(Modifier))
                     m_EliminateKeyUpEvents.Add(Modifier);
               }
               selected.Run(bKeyDown);
               bBlock = true;
            }
         }

         KeyInterceptor.DebugLine("Keyboard    : " + key + " Down: " + bKeyDown + "   => Block?" + bBlock);
         if (bBlock)
            return (System.IntPtr)1;
      }
      return CallNextHookEx(_hookID, nCode, wParam, lParam);
   }

   private static bool ModifierSet(int iVkey)
   {
      if (m_dicBuiltInToggles.ContainsKey(iVkey))
      {
         return Control.IsKeyLocked(m_dicBuiltInToggles[iVkey]);
      }
      else if (m_dicBuiltInModifiers.ContainsKey(iVkey))
      {
         return GetKeyState(m_dicBuiltInModifiers[iVkey]) == KeyStates.Down;
      }
      else
      {
         DebugLine("Check modifier :" + iVkey);
         return m_dicModifiers.ContainsKey(iVkey) && m_dicModifiers[iVkey];
      }
   }

   [DllImport("user32.dll", SetLastError = true)]
   public static extern Int32 SendMessage(
       int hWnd,               // handle to destination window
       int Msg,                // message
       int wParam,             // first message parameter
       int lParam);

   [DllImport("user32.dll", SetLastError = true)]
   static extern int FindWindow(string lpClassName, string lpWindowName);

   [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
   private static extern IntPtr SetWindowsHookEx(int idHook,
       LowLevelKeyboardProc lpfn, IntPtr hMod, uint dwThreadId);

   [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
   [return: MarshalAs(UnmanagedType.Bool)]
   private static extern bool UnhookWindowsHookEx(IntPtr hhk);

   [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
   private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode,
       IntPtr wParam, IntPtr lParam);

   [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
   private static extern IntPtr GetModuleHandle(string lpModuleName);

   [DllImport("user32.dll")]
   private static extern IntPtr GetDC(int hwnd);

   [DllImport("user32.dll")]
   private static extern int ReleaseDC(int hwnd, IntPtr hdc);

   [Flags]
   private enum KeyStates
   {
      None = 0,
      Down = 1,
      Toggled = 2
   }

   private static KeyStates GetKeyState(Keys key)
   {
      KeyStates state = KeyStates.None;

      short retVal = Win32.GetAsyncKeyState((int)key);

      //If the high-order bit is 1, the key is down
      //otherwise, it is up.
      if ((retVal & 0x8000) == 0x8000)
         state |= KeyStates.Down;

      return state;
   }

   public static bool IsKeyDown(Keys key)
   {
      return KeyStates.Down == (GetKeyState(key) & KeyStates.Down);
   }

   public static bool IsKeyToggled(Keys key)
   {
      return KeyStates.Toggled == (GetKeyState(key) & KeyStates.Toggled);
   }

   internal static void WriteMacros()
   {
      File.WriteAllLines(Path.Combine(Application.StartupPath, "macros.txt"),
            pMacros.Select(M => M.Serialize()).ToArray());
   }
}


